<?php

namespace Classes;

use Exception;

const UA_ANDROID = 'Mozilla/5.0 (Linux; Android 10.0; MHA-AL00 Build/HUAWEIMHA-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/43.0.2357.134 Mobile Safari/537.36';
const UA_IPHONE  = 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) Mobile/14F89 MicroMessenger/7.0.17(0x1700112a) NetType/WIFI Language/zh_CN';
const UA_PC      = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.54';
const UA_APPLET  = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36 MicroMessenger/7.0.9.501 NetType/WIFI MiniProgramEnv/Windows WindowsWechat';

class Curl {
	private $_cookie_jar;
	/**
	 * @var bool 请求状态标记
	 */
	private $_status = false;
	private $_max_read_size = 1024 * 1024;
	/**
	 * @var array 请求结果数据
	 */
	private $_info = [];
	/**
	 * @var null|Callable 拦截器回调函数
	 */
	private $_callback = null;
	private $_en_char = true;
	private $_charset;
	/**
	 * @var string 请求Content-Type
	 */
	private $_content_type = 'application/x-www-form-urlencoded';
	private $_method = 'GET';

	/**
	 * @var array<string,string> 请求头
	 */
	public $mHeader = [];
	/**
	 * @var array<string,string> 请求cookie
	 */
	public $mCookie = [];

	/**
	 * @var string|string[]> url地址
	 */
	public $mUrl = null;

	/**
	 * @var null|string|array url参数
	 */
	public $mParams = null;

	/**
	 * @var null|string|array post数据
	 */
	public $mFields = null;

	public $options = [
		CURLOPT_HEADER         => true,                    // 输出响应头
		CURLOPT_TIMEOUT        => 5,                       // 超时(秒)
		CURLOPT_CONNECTTIMEOUT => 5,                       // 连接超时
		CURLOPT_RETURNTRANSFER => true,                    // 输出数据流
		CURLOPT_FOLLOWLOCATION => true,                    // 自动跳转追踪
		CURLOPT_AUTOREFERER    => true,                    // 自动设置来路信息
		CURLOPT_SSL_VERIFYPEER => false,                   // 认证证书检查
		CURLOPT_SSL_VERIFYHOST => false,                   // 检查SSL加密算法
		CURLOPT_NOSIGNAL       => false,                   // 忽略所有传递的信号
		CURLOPT_HTTPHEADER     => [],                      // 请求头
		CURLINFO_HEADER_OUT    => true,                    // 获取请求头
		CURLOPT_ENCODING       => 'gzip, deflate'
	];

	private function __construct () {
		$this->_cookie_jar = charset(__DIR__, "GBK") . DIRECTORY_SEPARATOR . 'cookie.txt';
		$this->ua(true);
	}

	/**
	 * 实例化
	 *
	 * @return $this
	 */
	public static function init () {
		return new self();
	}

	/**
	 * @param  int    $key
	 * @param  mixed  $val
	 *
	 * @return $this
	 */
	public function opt ($key, $val = null) {
		if (!is_int($key)) return $this;

		if ($val === null) return $this->del($key);

		if (@$this->options[$key] !== $val) $this->reset()->options[$key] = $val;

		return $this;
	}

	/**
	 * 删除 CURLOPT_ 选项
	 *
	 * @param  int|int[]  $key
	 *
	 * @return $this
	 */
	public function del ($key) {
		if (is_array($key))
			foreach ($key as $v) $this->del($v);
		else if (array_key_exists($key, $this->options)) {
			unset($this->options[$key]);
			$this->reset();
		}

		return $this;
	}

	/**
	 * Cookie自动化，默认关闭
	 *
	 * @param  bool  $bool
	 *
	 * @return $this
	 */
	public function autoCookie ($bool = false) {
		$val = $bool ? $this->_cookie_jar : null;

		return $this->opt(CURLOPT_COOKIEFILE, $val)
			->opt(CURLOPT_COOKIEJAR, $val);
	}

	/**
	 * 设置超时时间
	 *
	 * @param  int  $timeout
	 *
	 * @return $this
	 */
	public function timeout ($timeout = 5, $connect_timeout = 5) {
		return $this->opt(CURLOPT_TIMEOUT, $timeout)
			->opt(CURLOPT_CONNECTTIMEOUT, $connect_timeout);
	}

	/**
	 * 自动重定向，默认开启
	 *
	 * @param  bool|int  $redirect
	 *
	 * @return $this
	 */
	public function location ($redirect = true) {
		return $this->redirect($redirect);
	}

	/**
	 * 自动重定向，默认开启
	 *
	 * @param  bool|int  $redirect
	 *
	 * @return $this
	 */
	public function redirect ($redirect = true) {
		if (is_int($redirect) && $redirect >= 0) $this->opt(CURLOPT_MAXREDIRS, $redirect);

		return $this->opt(CURLOPT_FOLLOWLOCATION, boolval($redirect));
	}

	/**
	 * 自定义User-Agent
	 *
	 * @param  bool|string  $agent
	 *
	 * @return $this
	 */
	public function ua ($agent = 'auto') {
		return $this->agent($agent);
	}

	/**
	 * 自定义User-Agent
	 *
	 * @param  bool|string  $agent
	 *
	 * @return $this
	 */
	public function agent ($agent = 'auto') {
		if (preg_match('/^(m|wap|h5|android|mobile)$/i', $agent))
			$agent = UA_ANDROID;
		else if (preg_match('/^(iphone|ios)$/i', $agent))
			$agent = UA_IPHONE;
		else if (preg_match('/^(pc|web|win|computer)$/i', $agent))
			$agent = UA_PC;
		else if (preg_match('/^(applet|wx|weixin)$/i', $agent))
			$agent = UA_APPLET;
		else if (strcasecmp($agent, 'auto') === 0 || $agent === true)
			$agent = @$_SERVER['HTTP_USER_AGENT'];
		else if ($agent === false || $agent === '')
			$agent = null;

		return $this->opt(CURLOPT_USERAGENT, $agent);
	}

	/**
	 * 自定义来路(Referer)
	 *
	 * @param  string  $ref
	 *
	 * @return $this
	 */
	public function referer ($ref = true) {
		return $this->ref($ref);
	}

	/**
	 * 自定义来路(Referer)
	 *
	 * @param  bool|string  $ref
	 *
	 * @return $this
	 */
	public function ref ($ref = true) {
		if (is_bool($ref)) return $this->opt(CURLOPT_AUTOREFERER, $ref);

		return $this->opt(CURLOPT_REFERER, $ref);
	}

	/**
	 * 请求鉴权（Authorization: Bearer ***）
	 *
	 * @param  string  $auth
	 * @param  string  $type
	 *
	 * @return $this
	 */
	public function auth ($auth, $type = 'Basic') {
		return $this->addHeader('Authorization', $type . ' ' . $auth);
	}

	/**
	 * @param  string  $encoding
	 *
	 * @return $this
	 */
	public function encoding ($encoding = 'gzip, deflate') {
		return $this->opt(CURLOPT_ENCODING, $encoding);
	}

	/**
	 * 添加自定义请求头
	 *
	 * @param  string|string[]|array<string,mixed>  $key
	 * @param  string                               $val
	 *
	 * @return $this
	 */
	public function header ($key, $val = null) {
		return $this->addHeader($key, $val);
	}

	/**
	 * 添加自定义请求头
	 *
	 * @param  string|string[]|array<string,mixed>  $key
	 * @param  string                               $val
	 *
	 * @return $this
	 */
	public function addHeader ($key, $val = null) {
		if (!is_array($key) && !is_string($key)) return $this;

		if (is_array($key)) {
			$is_associative = array_is_associative($key);
			foreach ($key as $k => $v) $is_associative ? $this->addHeader($k, $v) : $this->addHeader($v);
		} else if ($val === null) {
			$key = self::parse_header($key, false);
			foreach ($key as $k => $v) $this->addHeader($k, $v);
		} else if ($key !== '') {
			$k = self::ucwords($key);
			if ($k === 'User-Agent') $this->ua($val);
			else if ($k === 'Referer') $this->ref($val);
			else if ($k === 'Cookie') $this->addCookie($val);
			else if ($k === 'Accept-Encoding') $this->encoding($val);
			else $this->reset()->mHeader[$key] = $val;
		}

		return $this;
	}

	/**
	 * 添加自定义cookie
	 *
	 * @param  string|string[]|array<string,mixed>  $key
	 * @param  string                               $val
	 *
	 * @return $this
	 */
	public function cookie ($key, $val = null) {
		return $this->addCookie($key, $val);
	}

	/**
	 * 添加自定义cookie
	 *
	 * @param  string|string[]|array<string,mixed>  $key
	 * @param  string                               $val
	 *
	 * @return $this
	 */
	public function addCookie ($key, $val = null) {
		if (!is_array($key) && !is_string($key)) return $this;

		if (is_array($key)) {
			$is_associative = array_is_associative($key);
			foreach ($key as $k => $v) $is_associative ? $this->addCookie($k, $v) : $this->addCookie($v);
		} else if ($val === null) {
			$key = self::parse_cookie($key);
			foreach ($key as $k => $v) $this->addCookie($k, $v);
		} else if ($key !== '' && @$this->mCookie[$key] !== $val) {
			$this->reset()->mCookie[$key] = rawurldecode($val);
		}

		return $this;
	}

	/**
	 * 请求头：X-Requested-With
	 *
	 * @param  string  $val
	 *
	 * @return $this
	 */
	public function ajax ($val = 'XMLHttpRequest') {
		return $this->xhr($val);
	}

	/**
	 * 请求头：X-Requested-With
	 *
	 * @param  string  $val
	 *
	 * @return $this
	 */
	public function xhr ($val = 'XMLHttpRequest') {
		return $this->addHeader('X-Requested-With', $val);
	}

	/**
	 * @param  string  $charset
	 *
	 * @return $this
	 */
	public function charset ($charset = null) {
		$this->_charset = $charset;

		return $this;
	}

	/**
	 * 对params额外要编码的单字节字符
	 *
	 * @param  true|string  $en_char  true默认：+
	 *
	 * @return $this
	 */
	public function en_char ($en_char = true) {
		$this->_en_char = $en_char;

		return $this->reset();
	}

	/**
	 * @param  null|int  $size
	 *
	 * @return $this
	 */
	public function max_read_size ($size = 1024 * 1024) {
		$this->_max_read_size = $size;

		return $this;
	}

	/**
	 * 获取、设置 请求方法
	 *
	 * @param  null|string  $method  GET、POST、HEAD、PUT等
	 *
	 * @return $this|string
	 */
	public function method ($method = null) {
		if (!is_string($method) || $method === '') return $this->_method;

		$this->_method = strtoupper($method);

		return $this->opt(CURLOPT_CUSTOMREQUEST, $this->_method);
	}

	/**
	 * @param  bool  $nobody
	 *
	 * @return $this
	 */
	public function nobody ($nobody = true) {
		return $this->opt(CURLOPT_NOBODY, $nobody);
	}

	/**
	 * GET请求
	 *
	 * @param  string|array       $url     url地址
	 * @param  null|string|array  $params  查询参数
	 *
	 * @return $this
	 */
	public function get ($url, $params = null) {
		return $this->method('GET')
			->raw($url, null, $params, $this->_content_type);
	}

	/**
	 * PUT请求
	 *
	 * @param  string|array       $url     url地址
	 * @param  null|string|array  $fields  表单参数
	 * @param  null|string|array  $params  查询参数
	 *
	 * @return $this
	 */
	public function put ($url, $fields = null, $params = null) {
		return $this->method('PUT')
			->raw($url, $fields, $params, $this->_content_type);
	}

	/**
	 * POST请求
	 *
	 * @param  string|array       $url     url地址
	 * @param  null|string|array  $fields  表单参数
	 * @param  null|string|array  $params  查询参数
	 *
	 * @return $this
	 */
	public function post ($url, $fields = null, $params = null) {
		return $this->method('POST')
			->raw($url, $fields, $params, $this->_content_type);
	}

	/**
	 * JSON请求
	 *
	 * @param  string|array       $url     url地址
	 * @param  null|string|array  $fields  表单参数
	 * @param  null|string|array  $params  查询参数
	 *
	 * @return $this
	 */
	public function json ($url, $fields = null, $params = null) {
		return $this->method('POST')
			->raw($url, $fields, $params, 'json');
	}

	/**
	 * 上传文件请求
	 *
	 * @param  string|array       $url     url地址
	 * @param  array              $fields  表单参数
	 * @param  null|string|array  $params  查询参数
	 *
	 * @return $this
	 */
	public function upload ($url, $fields = [], $params = null) {
		return $this->method('POST')
			->raw($url, $fields, $params, 'form-data');
	}

	/**
	 * @param  string|array       $url     url地址
	 * @param  null|string|array  $fields  表单参数
	 * @param  null|string|array  $params  查询参数
	 * @param  null|string        $type    请求头Content-Type，常用的可简写/后面
	 *
	 * @return $this
	 */
	public function raw ($url, $fields = null, $params = null, $type = 'x-www-form-urlencoded') {
		if (!is_array($params)) $params = self::parse_str($params);
		if (!is_string($type)) $type = 'x-www-form-urlencoded';

		$this->mUrl    = is_array($url) ? $url : rtrim($url, '&');
		$this->mParams = $params;
		$this->mFields = $fields;

		return $this->content_type($type);
	}

	/**
	 * 拦截器
	 *
	 * @param  null|Callable  $callback  callable(\Classes\Curl $curl, int $index, resource $ch)
	 *
	 * @return $this
	 */
	public function interceptor ($callback = null) {
		$this->_callback = $callback !== null && is_callable($callback) ? $callback : null;

		return $this->reset();
	}

	/**
	 * 获取本地cookie_jar文件中的cookie
	 *
	 * @param  string  $url
	 *
	 * @return array
	 */
	public function getLocalCookie ($url) {
		$cookie = [];
		$url    = strval($url);
		if ($url !== '') {
			$cookie = @file_get_contents($this->_cookie_jar);
			$arr    = parse_url($url);
			$host   = preg_replace('/.*?\.(?=.+?\..+$)/', '', $arr['host']);
			$p      = preg_match_all('/' . preg_quote($host, '/') . '(?:\s+([^\s\r\n]+))(?:\s+([^\s\r\n]+))(?:\s+([^\s\r\n]+))(?:\s+([^\s\r\n]+))(?:\s+([^\s\r\n]+))(?:\s+([^\s\r\n]+))(?:\r*\n|$)/',
				rawurldecode($cookie), $m);
			if ($p > 0) $cookie = array_combine($m[5], $m[6]);

			return $cookie;
		}

		return $cookie;
	}

	/**
	 * 取响应cookie
	 *
	 * @param  bool      $list
	 * @param  int|null  $index  null取全部，-1取最后一个
	 *
	 * @return array|null
	 */
	public function getCookie ($list = false, $index = -1) {
		$cookie = $this->all($list ? 'cookies' : 'cookie');
		if ($cookie === null) return null;

		if ($this->_info['redirect_count'] === 0 || $index === null)
			return $cookie;
		else if ($index < 0 || $index >= count($cookie))
			return false === ($arr = end($cookie)) ? null : $arr;
		else
			return @$cookie[$index];
	}

	/**
	 * 获取响应头字符串
	 *
	 * @param  null|bool  $nobody
	 *
	 * @return string
	 */
	public function head ($nobody = null) {
		if (is_bool($nobody)) $this->nobody($nobody);

		return $this->all('header');
	}

	/**
	 * 获取响应头数据数组
	 *
	 * @param  null|bool    $nobody
	 * @param  null|string  $key
	 * @param  int          $index  -1取最后一个
	 *
	 * @return array|string|null
	 */
	public function heads ($nobody = null, $key = null, $index = -1) {
		if (is_bool($nobody)) $this->nobody($nobody);
		$key     = strval($key);
		$headers = $this->all('headers');

		if ($headers !== null && $key !== '') {
			$index = intval($index);
			if ($this->_info['redirect_count'] === 0)
				return @$headers[self::ucwords($key)];
			else if ($index < 0 || $index >= count($headers))
				return false === ($arr = end($headers)) ? null : @$arr[$key];
			else
				return @$headers[$index][$key];
		}

		return $headers;
	}

	/**
	 * @param  string  $charset  编码
	 *
	 * @return string
	 */
	public function html ($charset = null) {
		return html_entity_decode($this->response($charset), ENT_HTML5);
	}

	/**
	 * @param  bool  $associative
	 *
	 * @return false|mixed
	 */
	public function obj ($associative = false) {
		return is_json($this->response(), $associative);
	}

	/**
	 * @param  null|bool  $associative
	 * @param  int        $options
	 *
	 * @return false|mixed|\SimpleXMLElement
	 */
	public function xml ($associative = null, $options = LIBXML_NOCDATA) {
		$res    = $this->response();
		$result = simplexml_load_string($res, 'SimpleXMLElement', $options);
		if (is_bool($associative)) $result = is_json(json_encode($result), $associative);

		return $result;
	}

	/**
	 * 获取去掉html标签的响应主体内容（strip_tags）
	 *
	 * @param  string  $charset  编码
	 *
	 * @return null|string
	 */
	public function text ($charset = null) {
		return @strip_tags($this->response($charset));
	}

	/**
	 * 获取响应主体内容
	 *
	 * @param  string  $charset  编码
	 *
	 * @return null|string
	 */
	public function content ($charset = null) {
		return $this->response($charset);
	}

	/**
	 * 获取响应主体内容
	 *
	 * @param  string  $charset  编码
	 *
	 * @return null|string
	 */
	public function response ($charset = null) {
		return $this->charset($charset)->exec();
	}

	/**
	 * 获取响应代码
	 *
	 * @return int
	 */
	public function code () {
		return $this->all('http_code');
	}

	/**
	 * 获取重定向url地址
	 *
	 * @return string
	 */
	public function redirect_url () {
		return $this->all('redirect_url');
	}

	/**
	 * 获取最后1个有效url地址
	 *
	 * @return string
	 */
	public function url () {
		return $this->all('url');
	}

	/**
	 * 获取响应Content-Type或设置请求Content-Type
	 *
	 * @param  false|null|string  $content_type  false:获取响应content_type，null:删除
	 *
	 * @return string|$this
	 */
	public function content_type ($content_type = false) {
		if ($content_type === false) return $this->all('content_type');

		$types = [
			'x-www-form-urlencoded' => 'application/x-www-form-urlencoded',
			'form-data'             => 'multipart/form-data',
			'json'                  => 'application/json',
			'xml'                   => 'application/xml',
			'javascript'            => 'application/javascript',
			'plain'                 => 'text/plain',
			'html'                  => 'text/html'
		];
		if (!is_string($content_type) || $content_type === '') $content_type = null;
		else $content_type = strtolower($content_type);
		if (array_key_exists($content_type, $types)) $content_type = $types[$content_type];
		$this->_content_type = $content_type;

		return $this->addHeader('Content-Type', $content_type);
	}

	/**
	 * @param  string  $key
	 *
	 * @return array|string|int|null
	 */
	public function all ($key = null) {
		$this->exec();

		return !is_string($key) || $key === '' ? $this->_info : @$this->_info[$key];
	}

	/**
	 * 下载文件
	 *
	 * @param  string  $file  文件目录或完整路径
	 * @param  string  $name  文件名
	 *
	 * @return string|true
	 */
	public function down ($file = null, $name = null) {
		$_max_read_size = $this->_max_read_size;

		$file = strval($file);
		$name = strval($name);
		if ($file === '') $file = './' . pathinfo($this->options[CURLOPT_URL], PATHINFO_BASENAME);
		else if ($name !== '') $file = $file . DIRECTORY_SEPARATOR . $name;

		$data = $this->max_read_size(null)
			->nobody(false)
			->redirect()
			->exec();

		$this->max_read_size($_max_read_size);
		try {
			$fo = fopen($file, 'a');
			fwrite($fo, $data);
			fclose($fo);

			return true;
		} catch (Exception $e) {
			return $e->getMessage();
		}
	}

	/**
	 * 重置请求
	 *
	 * @param  string  ...$keys
	 *
	 * @return $this
	 */
	public function reset (...$keys) {
		foreach ($keys as $key) {
			switch ($key) {
				case 'params':
					$this->mParams = [];
					break;
				case 'fields':
					$this->mFields = null;
					break;
				case 'header':
					$this->mHeader = [];
					break;
				case 'cookie':
					$this->mCookie = [];
					break;
				case 'charset':
					$this->_charset = null;
					break;
			}
		}
		$this->_info   = [];
		$this->_status = false;

		return $this;
	}

	/**
	 * @param  null|callable  $callback  回调函数 callable(\Classes\Curl $curl, int $index, resource $ch)
	 *
	 * @return \Classes\Curl[]
	 * @throws \Exception
	 */
	public function multi ($callback = null) {
		if (!is_array($this->mUrl))
			throw new Exception("url地址类型错误，应为：string[]或array<int,array{url:string,params:null|string|array,fields:null|string|array,method:null|string}>");

		$curls   = [];
		$mParams = $this->mParams;
		$mFields = $this->mFields;
		$_method = $this->method();
		$_type   = $this->_content_type;
		foreach ($this->mUrl as $i => $val) {
			$url = $params = $fields = $method = $type = null;
			if (is_array($val)) extract($val);
			else $url = $val;

			if (!is_string($url) || !filter_var(url_encode($url), FILTER_VALIDATE_URL))
				throw new Exception("url地址类型错误，错误索引：{$i}");

			if ($params === null) $params = $mParams;
			else if (is_array($params) && is_array($mParams)) $params = array_merge($mParams, $params);
			if ($fields === null) $fields = $mFields;
			else if (is_array($fields) && is_array($mFields)) $fields = array_merge($mFields, $fields);
			if ($type === null) $type = $_type;
			if ($method === null) $method = $_method;

			$curls[$i] = clone $this;
			$curls[$i]->method($method)
				->raw($url, $fields, $params, $type);
		}

		return self::multiple($callback, ...$curls);
	}

	/**
	 * @param  null|callable  $callback  回调函数 callable(\Classes\Curl $curl, int $index, resource $ch)
	 * @param  \Classes\Curl  ...$curls
	 *
	 * @return \Classes\Curl[]
	 * @throws \Exception
	 */
	public static function multiple ($callback = null, ...$curls) {
		if (empty($curls)) throw new Exception("参数不能为空");
		$mh   = curl_multi_init();
		$conn = [];
		foreach ($curls as $i => $curl) {
			if (!$curl instanceof self) throw new Exception("参数类型错误，错误参数索引：{$i}");
			$conn[$i] = $curl->reset()->build_options($i);
			curl_multi_add_handle($mh, $conn[$i]);
		}

		$running = null;
		do {
			curl_multi_exec($mh, $running);
		} while ($running > 0);

		foreach ($conn as $i => $ch) {
			$data             = curl_multi_getcontent($ch);
			$curls[$i]->_info = curl_getinfo($ch);
			$curls[$i]->handle_response($data, $curls[$i]->_info);
			if (is_callable($callback)) call_user_func($callback, $curls[$i], $i, $ch);
			curl_multi_remove_handle($mh, $ch);
			curl_close($ch);
		}

		curl_multi_close($mh);

		return $curls;
	}

	/**
	 * 执行请求并返回数据
	 *
	 * @return null|string
	 */
	private function exec () {
		if ($this->_status) return @$this->_info['response'];

		$ch          = $this->reset()->build_options();
		$data        = curl_exec($ch);
		$this->_info = curl_getinfo($ch);
		curl_close($ch);

		return $this->handle_response($data, $this->_info);
	}

	/**
	 * 构建参数
	 *
	 * @param  int  $index
	 *
	 * @return false|resource
	 */
	private function build_options ($index = -1) {
		$ch = curl_init();
		if (is_callable($this->_callback)) call_user_func($this->_callback, $this, $index, $ch);

		$url    = url_encode(rtrim($this->mUrl, '&'), $this->_en_char);
		$params = $this->mParams;
		if ($params !== null && $params !== '') {
			if (is_array($params)) $params = arr2params($params, true);
			$arr          = parse_url($url);
			$arr['query'] = @$arr['query'] . '&' . $params;
			$arr['query'] = trim($arr['query'], '&');
			$url          = build_url($arr, $this->_en_char);
		}

		$this->opt(CURLOPT_URL, $url);
		if ($this->method() !== 'GET'/*!empty($this->mFields)*/) {
			$fields = $this->mFields;
			$type   = $this->_content_type;
			if ($fields !== null && is_string($type) && preg_match('#^(([\w-]+)/([\w-]+))#i', $type, $m)) {
				$is_array = is_array($fields) || $fields instanceof \stdClass;
				switch ($m[1]) {
					case 'application/x-www-form-urlencoded':
						if ($is_array) $fields = arr2params($fields, true);
						break;
					case 'multipart/form-data':
						if (!$is_array) $fields = self::parse_str($fields);
						break;
					case 'application/json':
						if ($is_array) $fields = arr2json($fields);
						break;
					case 'application/xml':
						if ($is_array) {
							$xml = new \SimpleXMLElement('<root/>');
							array_walk_recursive($fields, [$xml, 'addChild']);
							$fields = $xml->asXML();
						}
						break;
					case 'application/javascript':
					case 'text/plain':
					case 'text/html':
						break;
				}
			}
			$this->opt(CURLOPT_POSTFIELDS, $fields);
		}

		$this->options[CURLOPT_HTTPHEADER] = [];
		foreach ($this->mHeader as $key => $val)
			$this->options[CURLOPT_HTTPHEADER][] = self::ucwords($key) . ': ' . $val;
		if (!empty($this->mCookie))
			$this->options[CURLOPT_COOKIE] = arr2params($this->mCookie, true, '; ');

		curl_setopt_array($ch, $this->options);
		$this->_status = true;

		return $ch;
	}

	/**
	 * 处理结果
	 *
	 * @param  string  $data
	 * @param  array   $info
	 *
	 * @return string
	 */
	private function handle_response ($data, &$info) {
		$header_size = $info['header_size'];
		if ($data !== false && $header_size > 0) {
			$info['header']  = trim(substr($data, 0, $header_size));
			$redirect_count  = $info['redirect_count'];
			$info['cookie']  = $info['cookies'] = [];
			$arr             = preg_split('/(\r?\n){2,}/', $info['header'], $redirect_count + 1);
			$info['headers'] = array_map(function ($val) use (&$info) {
				$info['cookies'][] = $arr = self::parse_header_cookie($val);
				$keys              = array_column($arr, 'key');
				$vals              = array_column($arr, 'value');

				$info['cookie'][] = self::array_combine($keys, $vals, true);

				return self::parse_header($val);
			}, $arr);
			if ($redirect_count === 0) {
				$info['headers'] = current($info['headers']);
				$info['cookie']  = current($info['cookie']);
				$info['cookies'] = current($info['cookies']);
			}
			$info['response'] = substr($data, $header_size, $this->_max_read_size);
		}

		return charset(@$info['response'], $this->_charset);
	}

	/**
	 * 解析header字符串
	 *
	 * @param  string  $str        header字符串
	 * @param  bool    $recursive  重复key递归
	 *
	 * @return array
	 */
	private static function parse_header ($str, $recursive = true) {
		$header = [];
		$str    = strval($str);
		if (preg_match_all('/(?<=^|\n)\s*([\w-]+?)\s*:\s*(.*?)\s*(?=\r?\n|$)/', $str, $m) > 0)
			$header = self::array_combine($m[1], $m[2], $recursive, true);

		return $header;
	}

	/**
	 * 解析header中的Set-Cookie到list
	 *
	 * @param  string  $header
	 *
	 * @return array
	 */
	private static function parse_header_cookie ($header) {
		$parse_cookie = function ($str) {
			$p      = preg_match_all('/(?<=^|;)\s*([^=]+?)\s*=\s*(.*?)\s*(?=;|$)/', $str, $m);
			$i      = 0;
			$cookie = [];
			while ($i < $p) {
				$key = rawurldecode($m[1][$i]);
				$val = rawurldecode($m[2][$i]);
				if ($i === 0) {
					$cookie['key']   = $key;
					$cookie['value'] = $val;
				} else if (strcasecmp($key, 'expires') === 0) {
					$d            = date_create($val, timezone_open("Asia/Shanghai"));
					$cookie[$key] = $d->getTimestamp();
				} else
					$cookie[$key] = $val;
				$i++;
			}

			return $cookie;
		};

		$cookies = [];
		$p       = preg_match_all('/(?<=^|[\r\n])\s*Set-Cookie\s*:\s*(.+?)(?=[\r\n]|$)/i', $header, $m);
		$i       = 0;
		while ($i < $p) {
			$cookie = $parse_cookie($m[1][$i]);
			if (!empty($cookie)) $cookies[] = $cookie;
			$i++;
		}

		return $cookies;
	}

	/**
	 * @param  array   $keys
	 * @param  array   $values
	 * @param  bool    $recursive  重复key递归
	 * @param  string  $ucwords    key单词首字母大写
	 *
	 * @return array
	 */
	private static function array_combine ($keys, $values, $recursive = false, $ucwords = false) {
		$arr = [];
		foreach ($keys as $i => $key) {
			if ($ucwords) $key = self::ucwords($key);
			$value = @$values[$i];
			if (!array_key_exists($key, $arr) || !$recursive)
				$arr[$key] = $value;
			else if (is_array($arr[$key]))
				$arr[$key][] = $value;
			else
				$arr[$key] = [$arr[$key], $value];
		}

		return $arr;
	}

	/**
	 * 解析cookie字符串
	 *
	 * @param  string  $str
	 *
	 * @return array
	 */
	private static function parse_cookie ($str) {
		return self::parse_str($str, ';');
	}

	public static function parse_str ($str, $separator = '&', $splice = '=') {
		$ret = [];
		$str = trim(strval($str), $separator . $splice);
		$arr = explode($separator, $str);
		foreach ($arr as $val) {
			$arr2 = explode($splice, trim($val), 2);
			if ($arr2 && $arr2[0] !== '')
				$ret[rawurldecode($arr2[0])] = isset($arr2[1]) ? rawurldecode($arr2[1]) : null;
		}

		return $ret;
	}

	/**
	 * 英文单词首字母大写
	 *
	 * @param  string  $str
	 * @param  string  $separators  分割字符
	 *
	 * @return string
	 */
	public static function ucwords ($str, $separators = " -\t\r\n\f\v") {
		return ucwords(strval($str), $separators);
	}
}